home *** CD-ROM | disk | FTP | other *** search
/ Aminet 34 / Aminet 34 (2000)(Schatztruhe)[!][Dec 1999].iso / Aminet / util / gnu / unixcmds.lha / unixcmds / src / tail.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-10-06  |  13.0 KB  |  359 lines

  1. /* tail - copy the end of a file        Author: Norbert Schlenker */
  2.  
  3. /*   Syntax:    tail [-f] [-c number | -n number] [file]
  4.  *              tail -[number][c|l][f] [file]           (obsolescent)
  5.  *              tail +[number][c|l][f] [file]           (obsolescent)
  6.  *   Flags:
  7.  *      -c number       Measure starting point in bytes.  If number begins
  8.  *                      with '+', the starting point is relative to the
  9.  *                      the file's beginning.  If number begins with '-'
  10.  *                      or has no sign, the starting point is relative to
  11.  *                      the end of the file.
  12.  *      -f              Keep trying to read after EOF on files and FIFOs.
  13.  *      -n number       Measure starting point in lines.  The number
  14.  *                      following the flag has significance similar to
  15.  *                      that described for the -c flag.
  16.  *
  17.  *   If neither -c nor -n are specified, the default is tail -n 10.
  18.  *
  19.  *   In the obsolescent syntax, an argument with a 'c' following the
  20.  *   (optional) number is equivalent to "-c number" in the standard
  21.  *   syntax, with number including the leading sign ('+' or '-') of the
  22.  *   argument.  An argument with 'l' following the number is equivalent
  23.  *   to "-n number" in the standard syntax.  If the number is not
  24.  *   specified, 10 is used as the default.  If neither 'c' nor 'l' are
  25.  *   specified, 'l' is assumed.  The character 'f' may be suffixed to
  26.  *   the argument and is equivalent to specifying "-f" in the standard
  27.  *   syntax.  Look for lines marked "OBSOLESCENT".
  28.  *
  29.  *   If no file is specified, standard input is assumed. 
  30.  *
  31.  *   P1003.2 does not specify tail's behavior when a count of 0 is given.
  32.  *   It also does not specify clearly whether the first byte (line) of a
  33.  *   file should be numbered 0 or 1.  Historical behavior is that the
  34.  *   first byte is actually number 1 (contrary to all Unix standards).
  35.  *   Historically, a count of 0 (or -0) results in no output whatsoever,
  36.  *   while a count of +0 results in the entire file being copied (just like
  37.  *   +1).  The implementor does not agree with these behaviors, but has
  38.  *   copied them slavishly.  Look for lines marked "HISTORICAL".
  39.  *   
  40.  *   Author:    Norbert Schlenker
  41.  *   Copyright: None.  Released to the public domain.
  42.  *   Reference: P1003.2 section 4.59 (draft 10)
  43.  *   Notes:     Under Minix, this program requires chmem =30000.
  44.  *   Bugs:      No internationalization support; all messages are in English.
  45.  */
  46.  
  47. /* Force visible Posix names */
  48. #ifndef _POSIX_SOURCE
  49. #define _POSIX_SOURCE 1
  50. #endif
  51.  
  52. /* External interfaces */
  53. #include <sys/types.h>
  54. #include <sys/stat.h>
  55. #include <unistd.h>
  56. #include <ctype.h>
  57. #include <stdlib.h>
  58. #include <stdio.h>
  59. #include <dos/dos.h>
  60. #include <clib/dos_protos.h>
  61.  
  62. #include "getopt.h"
  63.  
  64. /* External interfaces that should have been standardized into <getopt.h> */
  65. #ifdef _MINIX
  66. int getopt  (int argc, char **argv, char *options);
  67. #else
  68. extern int getopt();
  69. #endif
  70. extern char *optarg;
  71. extern int optind;
  72.  
  73. /* We expect this constant to be defined in <limits.h> in a Posix program,
  74.  * but we'll specify it here just in case it's been left out.
  75.  */
  76. #ifndef LINE_MAX
  77. #define LINE_MAX 2048           /* minimum acceptable lower bound */
  78. #endif
  79.  
  80. /* Magic numbers suggested or required by Posix specification */
  81. #define SUCCESS 0               /* exit code in case of success */
  82. #define FAILURE 1               /*                   or failure */
  83. #define DEFAULT_COUNT 10        /* default number of lines or bytes */
  84. #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
  85. #define SLEEP_INTERVAL  1       /* sleep for one second intervals with -f */
  86.  
  87. #define sleep(t) Delay(t*50)    /* amigaDOS function */
  88.  
  89. #define FALSE 0
  90. #define TRUE 1
  91.  
  92. /* Internal functions - prototyped under Minix */
  93. int main  (int argc, char **argv);
  94. int tail  (int count, int bytes, int read_until_killed);
  95. int keep_reading  (void);
  96. void usage  (void);
  97.  
  98. int main(argc, argv)
  99. /* [<][>][^][v][top][bottom][index][help] */
  100. int argc;
  101. char *argv[];
  102. {
  103.   int cflag = FALSE;
  104.   int nflag = FALSE;
  105.   int fflag = FALSE;
  106.   int number = -DEFAULT_COUNT;
  107.   char *suffix;
  108.   int opt;
  109.   struct stat stat_buf;
  110.  
  111. /* Determining whether this invocation is via the standard syntax or
  112.  * via an obsolescent one is a nasty kludge.  Here it is, but there is
  113.  * no pretense at elegance.
  114.  */
  115.   if (argc == 1) {              /* simple:  default read of a pipe */
  116.         exit(tail(-DEFAULT_COUNT, 0, fflag));
  117.   }
  118.   if ((argv[1][0] == '+') ||    /* OBSOLESCENT */
  119.       (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
  120.                              (argv[1][1] == 'l') ||
  121.                              (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
  122.         --argc; ++argv;
  123.         if (isdigit(argv[0][1])) {
  124.                 number = (int)strtol(argv[0], &suffix, 10);
  125.                 if (number == 0) {              /* HISTORICAL */
  126.                         if (argv[0][0] == '+')
  127.                                 number = 1;
  128.                         else
  129.                                 exit(SUCCESS);
  130.                 }
  131.         } else {
  132.                 number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
  133.                 suffix = &(argv[0][1]);
  134.         }
  135.         if (*suffix != '\0') {
  136.                 if (*suffix == 'c') {
  137.                         cflag = TRUE;
  138.                         ++suffix;
  139.                 }
  140.                 else
  141.                 if (*suffix == 'l') {
  142.                         nflag = TRUE;
  143.                         ++suffix;
  144.                 }
  145.         }
  146.         if (*suffix != '\0') {
  147.                 if (*suffix == 'f') {
  148.                         fflag = TRUE;
  149.                         ++suffix;
  150.                 }
  151.         }
  152.         if (*suffix != '\0') {  /* bad form: assume to be a file name */
  153.                 number = -DEFAULT_COUNT;
  154.                 cflag = nflag = FALSE;
  155.                 fflag = FALSE;
  156.         } else {
  157.                 --argc; ++argv;
  158.         }
  159.   } else {                      /* new standard syntax */
  160.         while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
  161.                 switch (opt) {
  162.                       case 'c':
  163.                         cflag = TRUE;
  164.                         if (*optarg == '+' || *optarg == '-')
  165.                                 number = atoi(optarg);
  166.                         else
  167.                         if (isdigit(*optarg))
  168.                                 number = -atoi(optarg);
  169.                         else
  170.                                 usage();
  171.                         if (number == 0) {              /* HISTORICAL */
  172.                                 if (*optarg == '+')
  173.                                         number = 1;
  174.                                 else
  175.                                         exit(SUCCESS);
  176.                         }
  177.                         break;
  178.                       case 'f':
  179.                         fflag = TRUE;
  180.                         break;
  181.                       case 'n':
  182.                         nflag = TRUE;
  183.                         if (*optarg == '+' || *optarg == '-')
  184.                                 number = atoi(optarg);
  185.                         else
  186.                         if (isdigit(*optarg))
  187.                                 number = -atoi(optarg);
  188.                         else
  189.                                 usage();
  190.                         if (number == 0) {              /* HISTORICAL */
  191.                                 if (*optarg == '+')
  192.                                         number = 1;
  193.                                 else
  194.                                         exit(SUCCESS);
  195.                         }
  196.                         break;
  197.                       default:
  198.                         usage();
  199.                         /* NOTREACHED */
  200.                 }
  201.         }
  202.         argc -= optind;
  203.         argv += optind;
  204.   }
  205.  
  206.   if (argc > 1 ||               /* too many arguments */
  207.       (cflag && nflag)) {       /* both bytes and lines specified */
  208.         usage();
  209.   }
  210.  
  211.   if (argc > 0) {               /* an actual file */
  212.         if (freopen(argv[0], "r", stdin) != stdin) {
  213.                 fputs("tail: could not open ", stderr);
  214.                 fputs(argv[0], stderr);
  215.                 fputs("\n", stderr);
  216.                 exit(FAILURE);
  217.         }
  218.         /* There is an optimization possibility here.  If a file is being
  219.          * read, we need not look at the front of it.  If we seek backwards
  220.          * from the end, we can (potentially) avoid looking at most of the
  221.          * file.  Some systems fail when asked to seek backwards to a point
  222.          * before the start of the file, so we avoid that possibility.
  223.          */
  224.         if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
  225.                 long offset = cflag ? (long)number : (long)number * LINE_MAX;
  226.  
  227.                 if (-offset < stat_buf.st_size)
  228.                         fseek(stdin, offset, SEEK_END);
  229.         }
  230.   } else {
  231.         fflag = FALSE;          /* force -f off when reading a pipe */
  232.   }
  233.   exit(tail(number, cflag, fflag));
  234.   /* NOTREACHED */
  235. }
  236.  
  237. int tail(count, bytes, read_until_killed)
  238. /* [<][>][^][v][top][bottom][index][help] */
  239. int count;                      /* lines or bytes desired */
  240. int bytes;                      /* TRUE if we want bytes */
  241. int read_until_killed;          /* keep reading at EOF */
  242. {
  243.   int c;
  244.   char *buf;                    /* pointer to input buffer */
  245.   char *buf_end;                /* and one past its end */
  246.   char *start;                  /* pointer to first desired character in buf */
  247.   char *finish;                 /* pointer past last desired character */
  248.   int wrapped_once = FALSE;     /* TRUE after buf has been filled once */
  249.  
  250. /* This is magic.  If count is positive, it means start at the count'th
  251.  * line or byte, with the first line or byte considered number 1.  Thus,
  252.  * we want to SKIP one less line or byte than the number specified.  In
  253.  * the negative case, we look backward from the end of the file for the
  254.  * (count + 1)'th newline or byte, so we really want the count to be one
  255.  * LARGER than was specified (in absolute value).  In either case, the
  256.  * right thing to do is:
  257.  */
  258.   --count;
  259.  
  260. /* Count is positive:  skip the desired lines or bytes and then copy. */
  261.   if (count >= 0) {
  262.         while (count > 0 && (c = getchar()) != EOF) {
  263.                 if (bytes || c == '\n')
  264.                         --count;
  265.         }
  266.         while ((c = getchar()) != EOF) {
  267.                 if (putchar(c) == EOF)
  268.                         return FAILURE;
  269.         }
  270.         if (read_until_killed)
  271.                 return keep_reading();
  272.         return ferror(stdin) ? FAILURE : SUCCESS;
  273.   }
  274.  
  275. /* Count is negative:  allocate a reasonably large buffer. */
  276.   if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
  277.         fputs("tail: out of memory\n", stderr);
  278.         return FAILURE;
  279.   }
  280.   buf_end = buf + (MIN_BUFSIZE + 1);
  281.  
  282. /* Read the entire file into the buffer. */
  283.   finish = buf;
  284.   while ((c = getchar()) != EOF) {
  285.         *finish++ = c;
  286.         if (finish == buf_end) {
  287.                 finish = buf;
  288.                 wrapped_once = TRUE;
  289.         }
  290.   }
  291.   if (ferror(stdin))
  292.         return FAILURE;
  293.  
  294. /* Back up inside the buffer.  The count has already been adjusted to
  295.  * back up exactly one character too far, so we will bump the buffer
  296.  * pointer once after we're done.
  297.  * 
  298.  * BUG: For large line counts, the buffer may not be large enough to
  299.  *      hold all the lines.  The specification allows the program to
  300.  *      fail in such a case - this program will simply dump the entire
  301.  *      buffer's contents as its best attempt at the desired behavior.
  302.  */
  303.   if (finish != buf || wrapped_once) {          /* file was not empty */
  304.         start = (finish == buf) ? buf_end - 1 : finish - 1;
  305.         while (start != finish) {
  306.                 if ((bytes || *start == '\n') && ++count == 0)
  307.                         break;
  308.                 if (start == buf) {
  309.                         start = buf_end - 1;
  310.                         if (!wrapped_once)      /* never wrapped: stop now */
  311.                                 break;
  312.                 } else {
  313.                         --start;
  314.                 }
  315.         }
  316.         if (++start == buf_end) {               /* bump after going too far */
  317.                 start = buf;
  318.         }
  319.         if (finish > start) {
  320.                 fwrite(start, 1, finish - start, stdout);
  321.         } else {
  322.                 fwrite(start, 1, buf_end - start, stdout);
  323.                 fwrite(buf, 1, finish - buf, stdout);
  324.         }
  325.   }
  326.   if (read_until_killed)
  327.         return keep_reading();
  328.   return ferror(stdout) ? FAILURE : SUCCESS;
  329. }
  330.  
  331. /* Wake at intervals to reread standard input.  Copy anything read to
  332.  * standard output and then go to sleep again.
  333.  */
  334. int keep_reading()
  335. /* [<][>][^][v][top][bottom][index][help] */
  336. {
  337.   int c;
  338.  
  339.   for (;;) {
  340.         sleep(SLEEP_INTERVAL);
  341.         clearerr(stdin);
  342.         while ((c = getchar()) != EOF) {
  343.                 if (putchar(c) == EOF)
  344.                         return FAILURE;
  345.         }
  346.         if (ferror(stdin))
  347.                 return FAILURE;
  348.   }
  349. }
  350.  
  351. /* Tell the user the standard syntax. */
  352. void usage()
  353. /* [<][>][^][v][top][bottom][index][help] */
  354. {
  355.   fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
  356.   exit(FAILURE);
  357. }
  358. /* [<][>][^][v][top][bottom][index][help] */
  359.